/*
 *  Platform_AudioHelper.cpp
 *  Fridge
 *
 *  Created by Jeremy Abel on 6/21/12.
 *  Copyright 2012 __MyCompanyName__. All rights reserved.
 *
 */

#include "Main.h"

#include "AudioHelper.h"

#include <Carbon/Carbon.h>
#include <AudioUnit/AudioUnit.h>

static AudioBufferList				*s_AudioBuffer;
static AudioUnit					s_AudioUnit;
static AudioDeviceID				s_InputDeviceID = 0;
static AudioStreamBasicDescription	s_OutputFormat, s_DeviceFormat;
static UInt32						s_AudioChannels, s_AudioSamples = 0;
static OSStatus AudioInputProc(void* inRefCon, AudioUnitRenderActionFlags* ioActionFlags, const AudioTimeStamp* inTimeStamp, UInt32 inBusNumber, UInt32 inNumberFrames, AudioBufferList* ioData);

void AudioHelper::Platform_Initialise()
{
	Component				component;
	ComponentDescription	description;
	OSStatus				err = noErr;
	UInt32					iParam;
	AURenderCallbackStruct	callback;
	
	// Open the AudioOutputUnit
	description.componentType			= kAudioUnitType_Output;
	description.componentSubType		= kAudioUnitSubType_HALOutput;
	description.componentManufacturer	= kAudioUnitManufacturer_Apple;
	description.componentFlags			= 0;
	description.componentFlagsMask		= 0;
	
	if ( component = FindNextComponent(NULL, &description) )
	{
		err = OpenAComponent( component, &s_AudioUnit );
		if (err != noErr)
		{
			s_AudioUnit = NULL;
			return;
		}
	}
	
	// Enable input on the AUHAL
	iParam = 1;
	err = AudioUnitSetProperty(s_AudioUnit, kAudioOutputUnitProperty_EnableIO, kAudioUnitScope_Input, 1, &iParam, sizeof(UInt32));
	if (err == noErr)
	{
		iParam = 0;
		err = AudioUnitSetProperty(s_AudioUnit, kAudioOutputUnitProperty_EnableIO, kAudioUnitScope_Output, 0, &iParam, sizeof(UInt32));
	}
	
	// Select the default input device
	iParam = sizeof( AudioDeviceID );
	err = AudioHardwareGetProperty(kAudioHardwarePropertyDefaultInputDevice, &iParam, &s_InputDeviceID);
	if (err != noErr)
	{
		FridgeLog("failed to get default input device\n");
		return;
	}
	
	// Set the current device to the default input unit
	err = AudioUnitSetProperty(s_AudioUnit, kAudioOutputUnitProperty_CurrentDevice, kAudioUnitScope_Global, 0, &s_InputDeviceID, sizeof(AudioDeviceID));
	if (err != noErr)
	{
		FridgeLog("failed to set AU input device\n");
		return;
	}
	
	// Set up render callback
	callback.inputProc = AudioInputProc;
	callback.inputProcRefCon = new AudioHelper;
	err = AudioUnitSetProperty(s_AudioUnit, kAudioOutputUnitProperty_SetInputCallback, kAudioUnitScope_Global, 0, &callback, sizeof(AURenderCallbackStruct));	
	
	// Get hardware device format
	iParam = sizeof( AudioStreamBasicDescription );
	err = AudioUnitGetProperty(s_AudioUnit, kAudioUnitProperty_StreamFormat, kAudioUnitScope_Input, 1, &s_DeviceFormat, &iParam);
	if (err != noErr)
	{
		FridgeLog("failed to get input device ASBD\n");
		return;
	}
	
	// Set up output format
	s_AudioChannels						= MACRO_MAX(s_DeviceFormat.mChannelsPerFrame, 2);
	s_OutputFormat.mChannelsPerFrame	= s_AudioChannels;
	s_OutputFormat.mSampleRate			= s_DeviceFormat.mSampleRate;
	s_OutputFormat.mFormatID			= kAudioFormatLinearPCM;
	s_OutputFormat.mFormatFlags			= kAudioFormatFlagIsFloat | kAudioFormatFlagIsPacked | kAudioFormatFlagIsNonInterleaved;
	s_OutputFormat.mBitsPerChannel		= sizeof(Float32) * 8;
	s_OutputFormat.mBytesPerFrame		= s_OutputFormat.mBitsPerChannel / 8;
	s_OutputFormat.mFramesPerPacket		= 1;
	s_OutputFormat.mBytesPerPacket		= s_OutputFormat.mBytesPerFrame; 
	s_OutputFormat.mReserved			= 0;

	// Get the number of frames in the IO buffer(s)
	iParam = sizeof(UInt32);
	err = AudioUnitGetProperty(s_AudioUnit, kAudioDevicePropertyBufferFrameSize, kAudioUnitScope_Global, 0, &s_AudioSamples, &iParam);
	if(err != noErr)
	{
		FridgeLog("failed to get audio sample size\n");
		return;
	}
	
	// Initialize the AU
	err = AudioUnitInitialize(s_AudioUnit);
	if(err != noErr)
	{
		FridgeLog("failed to initialize AU\n");
		return;
	}
	
	// Allocate our audio buffers
	s_AudioBuffer = (AudioBufferList*)calloc(1, sizeof(AudioBufferList) + s_OutputFormat.mChannelsPerFrame * sizeof(AudioBuffer));
	if(s_AudioBuffer == NULL)
	{
		return;
	}
	
	s_AudioBuffer->mNumberBuffers = s_OutputFormat.mChannelsPerFrame;
	for(UInt32 i = 0; i < s_OutputFormat.mChannelsPerFrame; ++i) 
	{
		s_AudioBuffer->mBuffers[i].mNumberChannels = 1;
		s_AudioBuffer->mBuffers[i].mDataByteSize = s_AudioSamples * s_OutputFormat.mBytesPerFrame;
		s_AudioBuffer->mBuffers[i].mData = malloc (s_AudioSamples * s_OutputFormat.mBytesPerFrame);
		
		if(s_AudioBuffer->mBuffers[i].mData == NULL) 
		{
			//DestroyAudioBufferList(list);
			return;
		}
	}

	if(s_AudioBuffer == NULL)
	{
		FridgeLog("failed to allocate buffers\n");
		return;
	}
	
	// Ready!
	err = AudioOutputUnitStart(s_AudioUnit);
	if(err != noErr)
	{
		FridgeLog("failed to start AU\n");
		return;
	}
}

static OSStatus AudioInputProc(void* inRefCon, AudioUnitRenderActionFlags* ioActionFlags, const AudioTimeStamp* inTimeStamp, UInt32 inBusNumber, UInt32 inNumberFrames, AudioBufferList* ioData)
{
	OSStatus	err = noErr;
	
	// Render into audio buffer
	err = AudioUnitRender(s_AudioUnit, ioActionFlags, inTimeStamp, inBusNumber, inNumberFrames, s_AudioBuffer);
	if(err) 
	{
		FridgeLogf("AudioUnitRender() failed with error %i\n", err);
		return err;
	}
	
	return noErr;
}

void AudioHelper::Platform_Update()
{	
	// Buffer data pointers
	Float32 *pxLData = (Float32 *)s_AudioBuffer->mBuffers[0].mData;
	Float32 *pxRData = (Float32 *)s_AudioBuffer->mBuffers[1].mData;
	
	int iSamples = s_AudioBuffer->mBuffers[0].mDataByteSize / sizeof(Float32);
	
	for (int i = 0; i < iSamples * 2; i += 2) 
	{
		Float32 xLSample = pxLData[ i / 2 ];
		Float32 xRSample = pxRData[ i / 2 ];
		
		// We only get 512 samples, so just double them
		s_afLeftSamples[ i ]		= 
		s_afLeftSamples[ i - 1 ]	= static_cast< float >( xLSample ) * sizeof(Float32) / 2.0f; 
		s_afRightSamples[ i ]		= 	
		s_afRightSamples[ i - 1 ]	= static_cast< float >( xRSample ) * sizeof(Float32) / 2.0f;	
	}
}

void AudioHelper::Platform_Shutdown()
{
	// TODO: Should probably destroy audio buffers here
	
	// Stop pulling audio data
	OSStatus err = AudioOutputUnitStop(s_AudioUnit);
	if(err != noErr)
	{
		FridgeLog("failed to stop AU\n");
		return;
	}
	
	FridgeLog("Recording stopped.\n");
}

// eof
